/**
 * @file epd_class.c
 */
#include "epd_class.h"
#include <stdlib.h>
#include <string.h>

/* Compile-time setting: does hardware support 5V switching? Default false. */
#ifndef EPD_HW_SUPPORTS_5V
#define EPD_HW_SUPPORTS_5V 0
#endif

static inline uint32_t plane_size_bytes(uint16_t w, uint16_t h)
{
    return ((uint32_t)w * (uint32_t)h) / 8U;
}

EPD_Class *EPD_Create(uint16_t width, uint16_t height)
{
    EPD_Class *epd = (EPD_Class *)malloc(sizeof(EPD_Class));
    if (epd == NULL) { return NULL; }
    (void)memset(epd, 0, sizeof(EPD_Class));
    epd->dev.width = width;
    epd->dev.height = height;
    epd->plane_size_bytes = plane_size_bytes(width, height);

    /* Allocate double buffers */
    epd->front_bw = (uint8_t *)malloc(epd->plane_size_bytes);
    epd->front_ry = (uint8_t *)malloc(epd->plane_size_bytes);
    epd->back_bw  = (uint8_t *)malloc(epd->plane_size_bytes);
    epd->back_ry  = (uint8_t *)malloc(epd->plane_size_bytes);
    if ((epd->front_bw == NULL) || (epd->front_ry == NULL) ||
        (epd->back_bw == NULL) || (epd->back_ry == NULL)) {
        EPD_Destroy(epd);
        return NULL;
    }

    /* Initialize buffers to white */
    epd_gfx_clear_planes(epd->front_bw, epd->front_ry, epd->plane_size_bytes);
    epd_gfx_clear_planes(epd->back_bw, epd->back_ry, epd->plane_size_bytes);

    epd->volt_mode = EPD_VOLT_3V3;
    epd->dev.refresh_rate_hz = 1.0; /* default advisory */
    return epd;
}

void EPD_Destroy(EPD_Class *epd)
{
    if (epd == NULL) { return; }
    free(epd->front_bw); epd->front_bw = NULL;
    free(epd->front_ry); epd->front_ry = NULL;
    free(epd->back_bw);  epd->back_bw  = NULL;
    free(epd->back_ry);  epd->back_ry  = NULL;
    free(epd);
}

epd_err_t EPD_Init(EPD_Class *epd, const epd_spi_config_t *cfg)
{
    if ((epd == NULL) || (cfg == NULL)) { return EPD_ERR_PARAM; }
    epd_err_t r = epd_hal_init_spi(&epd->hal, cfg);
    if (r != EPD_OK) { return r; }
    r = epd_ssd167x_init(&epd->dev);
    if (r != EPD_OK) { return r; }
    /* Clear once */
    (void)epd_ssd167x_clear(&epd->dev);
    return EPD_OK;
}

epd_err_t EPD_SetRefreshRate(EPD_Class *epd, double hz)
{
    if (epd == NULL) { return EPD_ERR_PARAM; }
    return epd_ssd167x_set_refresh_rate(&epd->dev, hz);
}

epd_err_t EPD_SetVoltageMode(EPD_Class *epd, epd_voltage_mode_t mode)
{
    if (epd == NULL) { return EPD_ERR_PARAM; }
#if (EPD_HW_SUPPORTS_5V == 0)
    if (mode == EPD_VOLT_5V0) {
        return EPD_ERR_UNSUPPORTED;
    }
#endif
    epd->volt_mode = mode;
    return EPD_OK;
}

epd_err_t EPD_DrawVerticalStripes(EPD_Class *epd)
{
    if (epd == NULL) { return EPD_ERR_PARAM; }
    const int rc = epd_gfx_build_vertical_tricolor_stripes(epd->back_bw, epd->back_ry, epd->dev.width, epd->dev.height);
    return (rc == 0) ? EPD_OK : EPD_ERR_HW;
}

epd_err_t EPD_LoadRGB565(EPD_Class *epd, const uint16_t *rgb565)
{
    if ((epd == NULL) || (rgb565 == NULL)) { return EPD_ERR_PARAM; }
    const int rc = epd_gfx_convert_rgb565_to_planes(rgb565, epd->back_bw, epd->back_ry, epd->dev.width, epd->dev.height);
    return (rc == 0) ? EPD_OK : EPD_ERR_HW;
}

epd_err_t EPD_Commit(EPD_Class *epd)
{
    if (epd == NULL) { return EPD_ERR_PARAM; }
    /* Display back buffer */
    epd_err_t r = epd_ssd167x_display(&epd->dev, epd->back_bw, epd->back_ry);
    if (r != EPD_OK) { return r; }
    /* Swap buffers – O(1), <1ms */
    uint8_t *tmp;
    tmp = epd->front_bw; epd->front_bw = epd->back_bw; epd->back_bw = tmp;
    tmp = epd->front_ry; epd->front_ry = epd->back_ry; epd->back_ry = tmp;
    return EPD_OK;
}

epd_err_t EPD_Sleep(EPD_Class *epd, bool deep)
{
    if (epd == NULL) { return EPD_ERR_PARAM; }
    return epd_ssd167x_sleep(&epd->dev, deep);
}